package cn.willcream.sfmg;

import cn.willcream.sfmg.part.NormalCodeGenPart;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import schemacrawler.inclusionrule.RegularExpressionInclusionRule;
import schemacrawler.schema.*;
import schemacrawler.schemacrawler.*;
import schemacrawler.schemacrawler.exceptions.SchemaCrawlerException;
import schemacrawler.tools.databaseconnector.DatabaseConnectionSource;
import schemacrawler.tools.databaseconnector.SingleUseUserCredentials;
import schemacrawler.tools.utility.SchemaCrawlerUtility;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.nio.CharBuffer;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;

public class Generator {
    protected DataSource dataSource;
    protected String schema;
    /**
     * 接受正则表达式，比如所有sys_开头的表：sys_.*
     */
    protected String[] tablePatterns;
    /**
     * 相对位置，会和包名合在一起形成最终java文件目录，因此一般src/main/java就好了
     */
    protected String baseDir;
    /**
     * 实体类包名
     */
    protected String entityPkgName;
    protected NormalCodeGenPart codeGenPart;
    /**
     * 缩进一次空格数量
     */
    protected int intendQty = 4;

    public static final class Builder {
        private DataSource dataSource;
        private String schema;
        /**
         * 接受正则表达式，比如所有sys_开头的表：sys_.*
         */
        private String[] tablePatterns;
        /**
         * 相对位置，会和包名合在一起形成最终java文件目录，因此一般src/main/java就好了
         */
        private String baseDir;
        /**
         * 实体类包名
         */
        private String entityPkgName;
        private NormalCodeGenPart codeGenPart;
        /**
         * 缩进一次空格数量
         */
        private int intendQty = 4;

        // region getter and setter

        public DataSource getDataSource() {
            return dataSource;
        }

        public Generator.Builder setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
            return this;
        }

        public String getSchema() {
            return schema;
        }

        public Generator.Builder setSchema(String schema) {
            this.schema = schema;
            return this;
        }

        public String[] getTablePatterns() {
            return tablePatterns;
        }

        public Generator.Builder setTablePatterns(String[] tablePatterns) {
            this.tablePatterns = tablePatterns;
            return this;
        }

        public String getBaseDir() {
            return baseDir;
        }

        public Generator.Builder setBaseDir(String baseDir) {
            this.baseDir = baseDir;
            return this;
        }

        public String getEntityPkgName() {
            return entityPkgName;
        }

        public Generator.Builder setEntityPkgName(String entityPkgName) {
            this.entityPkgName = entityPkgName;
            return this;
        }

        public NormalCodeGenPart getCodeGenPart() {
            return codeGenPart;
        }

        public Generator.Builder setCodeGenPart(NormalCodeGenPart codeGenPart) {
            this.codeGenPart = codeGenPart;
            return this;
        }

        public int getIntendQty() {
            return intendQty;
        }

        public Generator.Builder setIntendQty(int intendQty) {
            this.intendQty = intendQty;
            return this;
        }
        // endregion


        public Generator build() {
            // 检查参数
            assertNonNull(dataSource, "dataSource不能为空");
            assertNonNull(schema, "schema不能为空");
            assertNonNull(tablePatterns, "tablePatterns不能为空");
            assertNonNull(entityPkgName, "entityPkgName不能为空");

            if (codeGenPart == null) {
                codeGenPart = new NormalCodeGenPart();
            }
            return new Generator(this);
        }

        private void assertNonNull(Object o, String tip) {
            if (o == null) {
                throw new SfmgException(tip);
            }
        }

    }

    private Generator(Builder builder) {
        this.dataSource = builder.dataSource;
        this.schema = builder.schema;
        this.tablePatterns = builder.tablePatterns;
        this.baseDir = builder.baseDir;
        this.entityPkgName = builder.entityPkgName;
        this.codeGenPart = builder.codeGenPart;
        this.intendQty = builder.intendQty;
    }

    private Generator(DataSource dataSource, String schema, String[] tablePatterns, String baseDir, String entityPkgName,
                      NormalCodeGenPart codeGenPart, int intendQty) {
        this.dataSource = dataSource;
        this.schema = schema;
        this.tablePatterns = tablePatterns;
        this.baseDir = baseDir;
        this.entityPkgName = entityPkgName;
        this.codeGenPart = codeGenPart;
        this.intendQty = intendQty;
    }

    public static Generator.Builder builder() {
        return new Generator.Builder();
    }

    public void run() {
        // 1. 检索数据库
        Catalog catalog = null;
        try {
            catalog = checkDatabase();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (SchemaCrawlerException e) {
            e.printStackTrace();
        }

        String intent = intentStr();

        // 2. 生成java文件
        for (final Schema schema : catalog.getSchemas()) {
            System.out.println(schema);
            for (final Table table : catalog.getTables(schema)) {
                System.out.print("o--> " + table);
                if (table instanceof View) {
                    System.out.println(" (VIEW)");
                } else {
                    System.out.println();
                }

                TypeSpec.Builder typeBuilder = codeGenPart.genClassSkeleton(table);

                for (final Column column : table.getColumns()) {
                    System.out.println("     o--> " + column + " (" + column.getColumnDataType() + ")");
                    FieldSpec field = codeGenPart.genField(column);
                    typeBuilder.addField(field);
                    codeGenPart.genFieldGetterAndSetter(column, typeBuilder);
                }
                JavaFile javaFile = JavaFile.builder(entityPkgName, typeBuilder.build()).indent(intent).build();
                File dir = new File(baseDir);
                try {
                    javaFile.writeTo(dir);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String intentStr() {
        StringBuilder sb = new StringBuilder();
        int n = 0;
        while (n < intendQty) {
            sb.append(" ");
            n++;
        }
        return sb.toString();
    }


    private Catalog checkDatabase() throws SQLException, SchemaCrawlerException {
        final LimitOptionsBuilder limitOptionsBuilder =
                LimitOptionsBuilder.builder()
                        .includeSchemas(new RegularExpressionInclusionRule(schema));
        for (String p : tablePatterns) {
            limitOptionsBuilder.includeTables(new RegularExpressionInclusionRule(p));
        }

        final LoadOptionsBuilder loadOptionsBuilder =
                LoadOptionsBuilder.builder()
                        // Set what details are required in the schema - this affects the
                        // time taken to crawl the schema
                        .withSchemaInfoLevel(SchemaInfoLevelBuilder.standard());
        final SchemaCrawlerOptions options =
                SchemaCrawlerOptionsBuilder.newSchemaCrawlerOptions()
                        .withLimitOptions(limitOptionsBuilder.toOptions())
                        .withLoadOptions(loadOptionsBuilder.toOptions());

        Catalog catalog = null;
        try (Connection connection = dataSource.getConnection()) {
            catalog = SchemaCrawlerUtility.getCatalog(connection, options);
        }

        return catalog;
    }


}
